package tp.badugi.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import tp.badugi.net.ServerNetworkingChannel;
import tp.badugi.net.iServerRequestListener;
import tp.badugi.net.iTableRequestListener;
import tp.badugi.net.exceptions.IllegalCharactersException;
import tp.badugi.net.exceptions.IllegalParameterValueException;
import tp.badugi.net.exceptions.NameTakenException;
import tp.badugi.net.exceptions.NoSuchTableException;
import tp.badugi.net.exceptions.ServerCouldNotCompleteRequestException;
import tp.badugi.net.exceptions.TableFullException;

public class Server implements iServerRequestListener {
	
	public static void main(String[] args) {
		serverSingleton = new Server();
		try {
			final int port = Integer.parseInt(args[0]);
			ServerSocket serverSocket = new ServerSocket(port);
			String currentTime = serverSingleton.getTime();
			System.out.println(currentTime + ": Server started on port " + port);
			while(!Thread.interrupted()) {
				Socket socket = serverSocket.accept();
				if (VERBOSE) {
					String time = serverSingleton.getTime();
					System.out.println(time + ": Client connected.");
				}
				ServerNetworkingChannel clientConnection = 
						new ServerNetworkingChannel(serverSingleton, socket);
				Thread clientThread = new Thread(clientConnection);
				clientThread.setDaemon(true);
				clientThread.setName("Client Thread");
				clientThread.start();
			}
			serverSocket.close();
		}
		catch(IndexOutOfBoundsException | NumberFormatException e) {
			System.out.println("Usage: server <port number>");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static final boolean VERBOSE = true;
	protected List<Table> tableList = new ArrayList<Table>();

	protected static Server serverSingleton;

	@Override
	public synchronized iTableRequestListener createTable (
			ServerNetworkingChannel netChannel,
			String myPlayerName, 
			String tableName,
			int maxPlayers, 
			long startingChips, 
			long smallBlind, 
			long bigBlind
	) throws NameTakenException,
			IllegalCharactersException, 
			IllegalParameterValueException,
			ServerCouldNotCompleteRequestException 
	{
		if (inTableList(tableName)) 
			throw new NameTakenException("The table name has been taken.");
		if (stringContainsIllegalCharacters(tableName)) 
			throw new IllegalCharactersException("The table name contains illegal characters.");
		if (stringContainsIllegalCharacters(myPlayerName)) 
			throw new IllegalCharactersException("The player name contains illegal characters.");
		if (maxPlayers < 2 || 6 < maxPlayers)
			throw new IllegalParameterValueException("Max players must be between 2 and 6.");
		if (startingChips < 10 * bigBlind || 1000000000 < startingChips)
			throw new IllegalParameterValueException("Incorrect amount for starting chips.");
		if (smallBlind <= 0 || smallBlind > 100000)
			throw new IllegalParameterValueException("Incorrect amount for small blind.");
		if (bigBlind <= smallBlind || smallBlind > 1000000)
			throw new IllegalParameterValueException("Incorrect amount for big blind.");
			
		
		Table table = new Table(this, tableName, maxPlayers, startingChips);
		if (VERBOSE) {
			System.out.println(getTime() + ": Created table \"" + tableName + "\"");
		}
		try {
			Player player = table.addPlayer(myPlayerName, netChannel);
			if (VERBOSE) {
				System.out.println(getTime() + ": Player " + player.getName() 
						+ " joined table \"" + tableName + "\"");
			}
			synchronized(this) {
				tableList.add(table);
			}
			
			Thread tableThread = new Thread(table);
			tableThread.setDaemon(true);
			tableThread.setName("Table Thread: " + tableName);
			tableThread.start();
			
			return player;
		} catch (TableFullException e) {
			System.err.println("Adding player to new table - this exception should never happen.");
			e.printStackTrace();
			throw new ServerCouldNotCompleteRequestException("Request failed on the server side.");
		}
	}

	@Override
	public synchronized String[] getTableNamesList() {
		List<String> tableNamesList = new ArrayList<String>();
		for (Table table : tableList) {
			tableNamesList.add(table.getName());
		}
		String[] tableNamesArray = new String[tableNamesList.size()];
		tableNamesArray = tableNamesList.toArray(tableNamesArray);
		return tableNamesArray;
	}

	public String getTime() {
		DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
		Calendar cal = Calendar.getInstance();
		String time = dateFormat.format(cal.getTime());
		return time;
	}

	@Override
	public iTableRequestListener joinTable(
			ServerNetworkingChannel netChannel, 
			String tableName, 
			String myPlayerName
	) throws TableFullException,
			NameTakenException,
			NoSuchTableException,
			IllegalCharactersException, 
			ServerCouldNotCompleteRequestException
	{
		if (!inTableList(tableName)) 
			throw new NoSuchTableException("The table does not exist.");
		if (stringContainsIllegalCharacters(myPlayerName)) 
			throw new IllegalCharactersException("The player name contains illegal characters.");
		Table table = getTableByName(tableName);
		Player player = table.addPlayer(myPlayerName, netChannel);
		if (VERBOSE) {
			String time = getTime();
			String playerName = player.getName();
			System.out.println(
					time + ": Player " + playerName + " joined table \"" + tableName + "\"");
		}
		return player;
	}
	
	public synchronized void removeTable(Table table) {
		boolean removed = tableList.remove(table);
		if (VERBOSE && removed) {
			System.out.println(getTime() + ": Removed table \"" + table.getName() + "\"");
		}
	}
	
	private synchronized boolean inTableList(String tableName) {
		for (Table table : tableList) {
			if (tableName.equals(table.getName())) {
				return true;
			}
		}
		return false;
	}

	protected synchronized Table getTableByName(String tableName) throws IllegalArgumentException {
		for (Table table : tableList) {
			String currentTablesName = table.getName();
			if (currentTablesName.equals(tableName)) {
				return table;
			}
		}
		// Otherwise
		throw new IllegalArgumentException("The table does not exist.");
	}
	
	protected boolean stringContainsIllegalCharacters(String string) {
		// Do not allow empty or white space strings
		if (string.trim().length() == 0) {
			return true;
		}
		
		// Do not allow characters used to encode packets
		for (int i = 0; i < string.length(); i++) {
			char c = string.charAt(i);
			if (	c == ';' || 
					c == '{' || 
					c == '}' || 
					c == '[' || 
					c == ']' || 
					c == '"' || 
					c == ':' || 
					c == ','
			) {
				return true;
			}
		}
		
		return false;
	}
	
}
